"""
Configuration-Driven Validator

A validator that executes validation rules defined in extension configuration files.
"""

import os
import re
import subprocess
from pathlib import Path
from typing import Dict, List, Optional, Any
import logging

from .base_validator import BaseValidator, ValidationResult as BaseValidationResult
from .config_parser import ExtensionDescriptor, ValidationRule

logger = logging.getLogger(__name__)


class ValidationResult:
    """Result of a validation check (internal use)."""
    
    def __init__(self, success: bool, message: str = "", details: Optional[Dict[str, Any]] = None):
        self.success = success
        self.message = message
        self.details = details or {}
        
    def __bool__(self) -> bool:
        return self.success


class ConfigDrivenValidator(BaseValidator):
    """
    Validator that executes validation rules from configuration descriptors.
    
    This validator reads validation rules from extension configuration files
    and executes them to check environment, configuration, and build outputs.
    """
    
    def __init__(self, extension_name: str, descriptor: ExtensionDescriptor):
        """
        Initialize the configuration-driven validator.
        
        Args:
            extension_name: Name of the extension this validator belongs to
            descriptor: Extension descriptor containing validation rules
        """
        super().__init__(extension_name)
        self.descriptor = descriptor
        self._validation_handlers = {
            'file_exists': self._validate_file_exists,
            'command_success': self._validate_command_success,
            'regex_match': self._validate_regex_match,
            'environment_var': self._validate_environment_var,
            'directory_not_empty': self._validate_directory_not_empty,
            'executable_exists': self._validate_executable_exists,
            'library_exists': self._validate_library_exists,
        }
    
    def validate_config(self, config_path: Path, config_data: Optional[Dict[str, Any]] = None) -> ValidationResult:
        """
        Validate a configuration file against the extension's schema.
        
        Args:
            config_path: Path to configuration file
            config_data: Optional parsed configuration data
            
        Returns:
            ValidationResult indicating success/failure
        """
        logger.info(f"Validating configuration for {self.extension_name}: {config_path}")
        
        if not self.descriptor.config_schema:
            logger.debug(f"No config schema defined for extension {self.extension_name}")
            return ValidationResult(success=True, message="No schema validation required")
        
        # For now, we'll do basic validation - a full JSON schema validator could be added later
        if not config_path.exists():
            return ValidationResult(
                success=False,
                message=f"Configuration file not found: {config_path}"
            )
        
        # Basic validation passed
        return ValidationResult(
            success=True,
            message="Configuration file exists and is accessible"
        )
    
    def validate_environment(self, source_path: Optional[Path] = None) -> ValidationResult:
        """
        Validate the runtime environment using configured validation rules.
        
        Args:
            source_path: Optional path to source directory
            
        Returns:
            ValidationResult indicating success/failure
        """
        logger.info(f"Validating environment for {self.extension_name}")
        
        if not self.descriptor.validation_rules:
            logger.debug(f"No validation rules defined for extension {self.extension_name}")
            return ValidationResult(success=True, message="No validation rules defined")
        
        failed_rules = []
        details = {}
        
        # Execute each validation rule
        for rule in self.descriptor.validation_rules:
            logger.debug(f"Executing validation rule: {rule.name}")
            
            try:
                result = self._execute_validation_rule(rule, source_path)
                details[rule.name] = {
                    'success': result.success,
                    'message': result.message,
                    'details': result.details
                }
                
                if not result.success:
                    failed_rules.append(f"{rule.name}: {result.message}")
                    
            except Exception as e:
                error_msg = f"Exception in validation rule '{rule.name}': {e}"
                logger.error(error_msg)
                failed_rules.append(f"{rule.name}: {error_msg}")
                details[rule.name] = {
                    'success': False,
                    'message': error_msg,
                    'details': {}
                }
        
        if failed_rules:
            return ValidationResult(
                success=False,
                message=f"Environment validation failed: {'; '.join(failed_rules)}",
                details=details
            )
        else:
            return ValidationResult(
                success=True,
                message=f"Environment validation passed ({len(self.descriptor.validation_rules)} rules)",
                details=details
            )
    
    def validate_build_output(self, source_path: Path, build_artifacts: List[str]) -> ValidationResult:
        """
        Validate build output artifacts.
        
        Args:
            source_path: Path to source directory
            build_artifacts: List of build artifacts to validate
            
        Returns:
            ValidationResult indicating success/failure
        """
        logger.info(f"Validating build output for {self.extension_name}")
        
        missing_artifacts = []
        invalid_artifacts = []
        details = {}
        
        # Check expected artifacts exist
        for expected_artifact in self.descriptor.build_artifacts:
            artifact_path = source_path / os.path.expandvars(expected_artifact)
            
            if not artifact_path.exists():
                missing_artifacts.append(str(artifact_path))
                details[expected_artifact] = {
                    'exists': False,
                    'path': str(artifact_path)
                }
            else:
                # Additional validation based on file type
                is_valid, validation_msg = self._validate_artifact_type(artifact_path)
                details[expected_artifact] = {
                    'exists': True,
                    'path': str(artifact_path),
                    'valid': is_valid,
                    'message': validation_msg
                }
                
                if not is_valid:
                    invalid_artifacts.append(f"{artifact_path}: {validation_msg}")
        
        errors = []
        if missing_artifacts:
            errors.append(f"Missing artifacts: {', '.join(missing_artifacts)}")
        if invalid_artifacts:
            errors.append(f"Invalid artifacts: {', '.join(invalid_artifacts)}")
        
        if errors:
            return ValidationResult(
                success=False,
                message=f"Build output validation failed: {'; '.join(errors)}",
                details=details
            )
        else:
            return ValidationResult(
                success=True,
                message=f"Build output validation passed ({len(self.descriptor.build_artifacts)} artifacts)",
                details=details
            )
    
    def get_validation_info(self) -> Dict[str, Any]:
        """
        Get information about available validations.
        
        Returns:
            Dictionary with validation information
        """
        return {
            'validator': self.__class__.__name__,
            'extension': self.extension_name,
            'descriptor_name': self.descriptor.name,
            'descriptor_version': self.descriptor.version,
            'validation_rules_count': len(self.descriptor.validation_rules),
            'build_artifacts_count': len(self.descriptor.build_artifacts),
            'supported_rule_types': list(self._validation_handlers.keys()),
            'has_config_schema': bool(self.descriptor.config_schema),
        }
    
    def can_validate(self, result_path: Path) -> bool:
        """
        Check if this validator can validate the given results.
        
        Args:
            result_path: Path to result file or directory
            
        Returns:
            True if validator can handle these results
        """
        # Config-driven validators can validate if they have validation rules defined
        if not self.descriptor.validation_rules:
            return False
        
        # Basic check - result path should exist
        return result_path.exists()
    
    def validate_correctness(self, result_path: Path, 
                           reference_path: Optional[Path] = None,
                           tolerance: float = 1e-6) -> BaseValidationResult:
        """
        Validate the correctness of solver results using configured rules.
        
        Args:
            result_path: Path to solver results
            reference_path: Path to reference solution (if available)
            tolerance: Numerical tolerance for comparisons
            
        Returns:
            ValidationResult indicating correctness
        """
        # For config-driven validation, we run environment validation with result path as context
        result = self.validate_environment(result_path)
        
        return BaseValidationResult(
            success=result.success,
            message=f"Correctness validation: {result.message}",
            details=result.details
        )
    
    def validate_convergence(self, result_path: Path,
                           expected_iterations: Optional[int] = None) -> BaseValidationResult:
        """
        Validate solver convergence behavior using configured rules.
        
        Args:
            result_path: Path to solver results
            expected_iterations: Expected number of iterations
            
        Returns:
            ValidationResult indicating convergence quality
        """
        # Check for convergence-related validation rules
        convergence_rules = [rule for rule in self.descriptor.validation_rules 
                           if 'convergence' in rule.name.lower() or 'iteration' in rule.name.lower()]
        
        if not convergence_rules:
            return BaseValidationResult(
                success=True,
                message="No convergence validation rules defined"
            )
        
        # Execute convergence-specific rules
        failed_rules = []
        details = {}
        
        for rule in convergence_rules:
            try:
                result = self._execute_validation_rule(rule, result_path.parent)
                details[rule.name] = {
                    'success': result.success,
                    'message': result.message,
                    'details': result.details
                }
                
                if not result.success:
                    failed_rules.append(f"{rule.name}: {result.message}")
                    
            except Exception as e:
                error_msg = f"Exception in convergence rule '{rule.name}': {e}"
                failed_rules.append(error_msg)
                details[rule.name] = {
                    'success': False,
                    'message': error_msg,
                    'details': {}
                }
        
        if failed_rules:
            return BaseValidationResult(
                success=False,
                message=f"Convergence validation failed: {'; '.join(failed_rules)}",
                details=details
            )
        else:
            return BaseValidationResult(
                success=True,
                message=f"Convergence validation passed ({len(convergence_rules)} rules)",
                details=details
            )
    
    def _execute_validation_rule(self, rule: ValidationRule, source_path: Optional[Path]) -> ValidationResult:
        """Execute a single validation rule."""
        handler = self._validation_handlers.get(rule.type)
        
        if not handler:
            return ValidationResult(
                success=False,
                message=f"Unknown validation rule type: {rule.type}"
            )
        
        try:
            return handler(rule, source_path)
        except Exception as e:
            return ValidationResult(
                success=False,
                message=f"Validation rule execution failed: {str(e)}"
            )
    
    def _validate_file_exists(self, rule: ValidationRule, source_path: Optional[Path]) -> ValidationResult:
        """Validate that a file exists."""
        target_path = Path(os.path.expandvars(rule.target))
        
        # Make path relative to source if not absolute
        if source_path and not target_path.is_absolute():
            target_path = source_path / target_path
        
        if target_path.exists():
            return ValidationResult(
                success=True,
                message=f"File exists: {target_path}",
                details={'path': str(target_path), 'size': target_path.stat().st_size if target_path.is_file() else None}
            )
        else:
            return ValidationResult(
                success=False,
                message=f"File not found: {target_path}",
                details={'path': str(target_path)}
            )
    
    def _validate_command_success(self, rule: ValidationRule, source_path: Optional[Path]) -> ValidationResult:
        """Validate that a command executes successfully."""
        try:
            # Determine working directory
            working_dir = source_path or Path.cwd()
            
            # Prepare environment
            env = os.environ.copy()
            if rule.parameters and 'environment' in rule.parameters:
                env.update(rule.parameters['environment'])
            
            # Execute command
            timeout = rule.parameters.get('timeout', 30) if rule.parameters else 30
            result = subprocess.run(
                rule.target,
                shell=True,
                cwd=working_dir,
                env=env,
                capture_output=True,
                text=True,
                timeout=timeout
            )
            
            if result.returncode == 0:
                return ValidationResult(
                    success=True,
                    message=f"Command succeeded: {rule.target}",
                    details={'returncode': result.returncode, 'stdout': result.stdout}
                )
            else:
                return ValidationResult(
                    success=False,
                    message=f"Command failed with exit code {result.returncode}: {rule.target}",
                    details={'returncode': result.returncode, 'stderr': result.stderr}
                )
                
        except subprocess.TimeoutExpired:
            return ValidationResult(
                success=False,
                message=f"Command timed out: {rule.target}",
                details={'timeout': timeout}
            )
        except Exception as e:
            return ValidationResult(
                success=False,
                message=f"Command execution failed: {rule.target} - {str(e)}",
                details={'error': str(e)}
            )
    
    def _validate_regex_match(self, rule: ValidationRule, source_path: Optional[Path]) -> ValidationResult:
        """Validate that file content matches a regex pattern."""
        if not rule.parameters or 'pattern' not in rule.parameters:
            return ValidationResult(
                success=False,
                message="regex_match rule requires 'pattern' parameter"
            )
        
        target_path = Path(os.path.expandvars(rule.target))
        
        # Make path relative to source if not absolute
        if source_path and not target_path.is_absolute():
            target_path = source_path / target_path
        
        if not target_path.exists():
            return ValidationResult(
                success=False,
                message=f"Target file not found: {target_path}",
                details={'path': str(target_path)}
            )
        
        try:
            content = target_path.read_text(encoding='utf-8')
            pattern = rule.parameters['pattern']
            flags = 0
            
            if rule.parameters.get('case_insensitive', False):
                flags |= re.IGNORECASE
            if rule.parameters.get('multiline', False):
                flags |= re.MULTILINE
            
            match = re.search(pattern, content, flags)
            
            if match:
                return ValidationResult(
                    success=True,
                    message=f"Pattern matched in {target_path}",
                    details={'pattern': pattern, 'match': match.group(0), 'path': str(target_path)}
                )
            else:
                return ValidationResult(
                    success=False,
                    message=f"Pattern not found in {target_path}: {pattern}",
                    details={'pattern': pattern, 'path': str(target_path)}
                )
                
        except Exception as e:
            return ValidationResult(
                success=False,
                message=f"Failed to read file {target_path}: {str(e)}",
                details={'path': str(target_path), 'error': str(e)}
            )
    
    def _validate_environment_var(self, rule: ValidationRule, source_path: Optional[Path]) -> ValidationResult:
        """Validate that an environment variable is set correctly."""
        var_value = os.environ.get(rule.target)
        
        if var_value is None:
            return ValidationResult(
                success=False,
                message=f"Environment variable not set: {rule.target}",
                details={'variable': rule.target}
            )
        
        # Check expected value if specified
        if rule.parameters and 'expected_value' in rule.parameters:
            expected = rule.parameters['expected_value']
            if var_value != expected:
                return ValidationResult(
                    success=False,
                    message=f"Environment variable {rule.target} has unexpected value: got '{var_value}', expected '{expected}'",
                    details={'variable': rule.target, 'actual': var_value, 'expected': expected}
                )
        
        return ValidationResult(
            success=True,
            message=f"Environment variable set: {rule.target}={var_value}",
            details={'variable': rule.target, 'value': var_value}
        )
    
    def _validate_directory_not_empty(self, rule: ValidationRule, source_path: Optional[Path]) -> ValidationResult:
        """Validate that a directory exists and is not empty."""
        target_path = Path(os.path.expandvars(rule.target))
        
        # Make path relative to source if not absolute
        if source_path and not target_path.is_absolute():
            target_path = source_path / target_path
        
        if not target_path.exists():
            return ValidationResult(
                success=False,
                message=f"Directory not found: {target_path}",
                details={'path': str(target_path)}
            )
        
        if not target_path.is_dir():
            return ValidationResult(
                success=False,
                message=f"Path is not a directory: {target_path}",
                details={'path': str(target_path)}
            )
        
        try:
            contents = list(target_path.iterdir())
            if contents:
                return ValidationResult(
                    success=True,
                    message=f"Directory is not empty: {target_path}",
                    details={'path': str(target_path), 'item_count': len(contents)}
                )
            else:
                return ValidationResult(
                    success=False,
                    message=f"Directory is empty: {target_path}",
                    details={'path': str(target_path), 'item_count': 0}
                )
        except Exception as e:
            return ValidationResult(
                success=False,
                message=f"Failed to read directory {target_path}: {str(e)}",
                details={'path': str(target_path), 'error': str(e)}
            )
    
    def _validate_executable_exists(self, rule: ValidationRule, source_path: Optional[Path]) -> ValidationResult:
        """Validate that an executable exists and is executable."""
        target_path = Path(os.path.expandvars(rule.target))
        
        # Make path relative to source if not absolute
        if source_path and not target_path.is_absolute():
            target_path = source_path / target_path
        
        if not target_path.exists():
            return ValidationResult(
                success=False,
                message=f"Executable not found: {target_path}",
                details={'path': str(target_path)}
            )
        
        if not os.access(target_path, os.X_OK):
            return ValidationResult(
                success=False,
                message=f"File is not executable: {target_path}",
                details={'path': str(target_path)}
            )
        
        return ValidationResult(
            success=True,
            message=f"Executable exists and is executable: {target_path}",
            details={'path': str(target_path), 'size': target_path.stat().st_size}
        )
    
    def _validate_library_exists(self, rule: ValidationRule, source_path: Optional[Path]) -> ValidationResult:
        """Validate that a library exists."""
        target_path = Path(os.path.expandvars(rule.target))
        
        # Make path relative to source if not absolute
        if source_path and not target_path.is_absolute():
            target_path = source_path / target_path
        
        # Check common library extensions
        library_extensions = ['.so', '.a', '.dylib', '.dll', '.lib']
        
        if target_path.exists():
            lib_path = target_path
        else:
            # Try to find library with common extensions
            lib_path = None
            for ext in library_extensions:
                candidate = target_path.with_suffix(ext)
                if candidate.exists():
                    lib_path = candidate
                    break
        
        if lib_path and lib_path.exists():
            return ValidationResult(
                success=True,
                message=f"Library found: {lib_path}",
                details={'path': str(lib_path), 'size': lib_path.stat().st_size}
            )
        else:
            return ValidationResult(
                success=False,
                message=f"Library not found: {target_path} (tried extensions: {library_extensions})",
                details={'path': str(target_path)}
            )
    
    def _validate_artifact_type(self, artifact_path: Path) -> tuple[bool, str]:
        """Validate an artifact based on its file type."""
        if not artifact_path.exists():
            return False, "Artifact does not exist"
        
        suffix = artifact_path.suffix.lower()
        
        # Executables
        if suffix in ['.exe', ''] and os.access(artifact_path, os.X_OK):
            return True, "Valid executable"
        
        # Libraries
        if suffix in ['.so', '.a', '.dylib', '.dll', '.lib']:
            return True, "Valid library"
        
        # Archives
        if suffix in ['.tar', '.gz', '.zip', '.7z']:
            return True, "Valid archive"
        
        # Generic file validation
        if artifact_path.is_file() and artifact_path.stat().st_size > 0:
            return True, "Valid file"
        
        return False, "Unknown or invalid artifact type"